#pragma once

#include <iostream>
#include <queue>
#include <pthread.h>

const size_t num = 5;

template<class T>
class block_queue
{
public:
    block_queue(int size = num) : _cap(size)
    {
        pthread_mutex_init(&_mtx, nullptr);
        pthread_cond_init(&_is_full, nullptr);
        pthread_cond_init(&_is_empty, nullptr);
    }

    ~block_queue()
    {
        pthread_mutex_destroy(&_mtx);
        pthread_cond_destroy(&_is_full);
        pthread_cond_destroy(&_is_empty);
    }

    void push(const T& data)
    {
        lock();
        while (is_full())
            pdr_wait();

        _q.push(data);
        
        csr_wakeup();
        unlock();
    }

    void pop(T* data)
    {
        lock();
        while (is_empty())
            csr_wait();

        *data = _q.front();
        _q.pop();
        
        pdr_wakeup();
        unlock();
    }

private:
    void lock() { pthread_mutex_lock(&_mtx); }
    void unlock() { pthread_mutex_unlock(&_mtx); }
    void csr_wait() { pthread_cond_wait(&_is_full, &_mtx); }
    void pdr_wait() { pthread_cond_wait(&_is_empty, &_mtx); }
    void csr_wakeup() { pthread_cond_signal(&_is_full); }
    void pdr_wakeup() { pthread_cond_signal(&_is_empty); }

    bool is_full() { return _q.size() == _cap; }
    bool is_empty() { return _q.empty(); }

private:
    std::queue<T> _q;
    size_t _cap;

    pthread_mutex_t _mtx;
    pthread_cond_t _is_full; // consumer care it
    pthread_cond_t _is_empty;// producer care it
};

